/* Implements functions to access the UART */

#include "uart.h"

/* global variables, but local to this module ! */
static PCB* owner;
static fifo_queue uart_queue;

static char* in_buffer;
static int32_t in_buffer_length;

static const char* out_buffer;

static int32_t index;

/* indicates how the UART is being used */
static int32_t mode = UNUSED;

static internal_queue_t internal_queue;

static int32_t end_reading;

/* puts the character c in the internal queue. Returns FAILURE if the queue is full, SUCCESS otherwise. */
int32_t internal_queue_put(char c){
  if(internal_queue.count == INTERNAL_QUEUE_LENGTH)
    return FAILURE; //the queue is full
  internal_queue.queue[internal_queue.in] = c;
  internal_queue.in = (internal_queue.in+1)%INTERNAL_QUEUE_LENGTH;
  internal_queue.count ++;
  return SUCCESS;
}

/* gets the first character in the internal queue, copies it in c and removes it from the queue. Returns FAILURE if the queue is empty, SUCCESS otherwise. */
int32_t internal_queue_get(char* c){
  if(internal_queue.count == 0)
    return FAILURE; //the queue is empty
  *c = internal_queue.queue[internal_queue.out];
  internal_queue.out = (internal_queue.out+1)%INTERNAL_QUEUE_LENGTH;
  internal_queue.count --;
  return SUCCESS;
}

/* resets the internal queue */
void reset_internal_queue(){
  internal_queue.count = 0;
  internal_queue.in = 0;
  internal_queue.out = 0;
}

/* configures the UART */
void configure_uart(){ 
  /* set word length to 8 bits */
  tty0->lcr.field.wls = 3; 
  /* Some obscure bit that need to be set for UART interrupts to work. */
  tty0->mcr.field.out2 = 1;
  /* resets the internal queue */
  reset_internal_queue();
  /* resets the fifo queue */
  fifo_queue_reset(&uart_queue);
}

/* cleans the uart's receiving buffer of any data that might be left in it */
void clean_uart(){
  char c;
  while(tty0->lsr.field.dr)
    c = tty0->rbr;
}

/* sets the global variables with the correct values to print data */
void set_printing_mode(const char* data){
  out_buffer = data;
  index = 0;
  mode = PRINTING;
}

/* sets the global variables with the correct values to get input from the user and store it into data (up to length-1 characters) */
void set_reading_mode(char* data, int32_t length){
  in_buffer = data;
  in_buffer_length = length;
  end_reading = 0;
  index = 0;
  clean_uart();
  /* Generate interrupts when data is received by UART. */
  tty0->ier.field.erbfi = 1;
  mode = READING;
}

// function called by the interrupt handler. 
void uart_handler() {
  switch(mode){
  case READING:
    read();
    break;
  case PRINTING:
    print();
    break;
  default:
    ; //to be on the safe side but should not be possible
  }
}

int32_t get_uart(PCB* pcb) {
  if (NULL != owner && pcb_get_pid(pcb) != pcb_get_pid(owner)) {
    if(fifo_queue_put(&uart_queue, pcb) == OUT_OF_MEMORY || block_pcb(pcb) == FAILURE)
      return FAILURE; //fatal error here
    //the process is now waiting for IO
    pcb_set_state(pcb, WAITING_IO_UART_STATE);
    return IN_UART_QUEUE;
  }
  owner = pcb;
  return SUCCESS;
}

int32_t release_uart(){
  PCB* pcb;
  if(wake_up_pcb(owner) == FAILURE)
    return FAILURE;
  owner = NULL;
  //we get the first process in the queue that has not been terminated meanwhile
  pcb = (PCB*)fifo_queue_get(&uart_queue);
  if(pcb != NULL){
    //the new owner !
    if(wake_up_pcb(pcb) == FAILURE)
      return FAILURE; //impossible to wake the new owner up
    owner = pcb;
  }
  return SUCCESS;
}

/* removes PCB from the uart queue */
int32_t remove_uart_queue(PCB* pcb){
  return fifo_queue_remove(&uart_queue, pcb,compare_pcb, NULL);
}

/* tries to get a character from the UART. Waits for one if none is available. Stops when the character entered is \r or when in_buffer_length - 1 characters have been read, whichever comes first. */ 
void read() {
  char c;
  if (tty0->lsr.field.dr && !end_reading) {
    //a character is available
    c = tty0->rbr;
    if(c == '\r') {
      //we are done using the device but the internal queue might not be empty. So we just signal the end for now
      end_reading = 1;
    }else{
      //we need to print this character
      if(internal_queue_put(c) == FAILURE){
	//something went wrong, we cannot continue
	end_of_reading(FAILURE);
	return;
      }
      //some special characters don't go in the buffer
      switch(c){
      case 8: //backspace
	if(internal_queue_put(' ') == FAILURE || internal_queue_put(8) == FAILURE){
	  end_of_reading(FAILURE);
	  return;
	}
	if(index < in_buffer_length -1){
	  index --; //removes the previous character from the result
	  if(index < 0)
	    index = 0;
	}
	break;
      default:
	//something that goes into the buffer
	if(index < in_buffer_length - 1){
	  //max length not reached
	  in_buffer[index++] = c;
	}
      }
    }
  }
  //this code prints what the user entered 
  if(internal_queue.count){
    //there is something to print in the queue
    if(tty0->lsr.field.thre){
      //device ready to print
      internal_queue_get(&c);
      tty0->thr = c;
    }
    //sets the device to interrupt when it is done printing (if needed)
    tty0->ier.field.etbei = (internal_queue.count > 0);
  }else if(end_reading)
    //an empty internal queue and \r entered, we are done
    end_of_reading(SUCCESS);

}

/* prints a character from out_buffer and sets the device to interrupt when it is done printing (only if there are remaining characters to print) */ 
void print() {
  char c;
  if(tty0->lsr.field.thre){
    //device ready to print
    //characters in the internal queue have a higher priority
    if(internal_queue.count)
      internal_queue_get(&c);
    else {
      c = out_buffer[index++];
      //the \n character requires a special treatment 
      if(c == '\n'){
	c = '\r';
	if(internal_queue_put('\n') == FAILURE){
	  end_of_printing(FAILURE);
	  return;
	}
      }
    }
    tty0->thr = c;
  }
  if(out_buffer[index] == '\0' && !internal_queue.count)
    end_of_printing(SUCCESS); //done !
  else
    tty0->ier.field.etbei = 1; //not done yet !
}

/* ends the current use of the device and returns an error code that the owner */
int32_t end_of_io(){
  if(mode == READING)
    return end_of_reading(FAILURE);
  if(mode == PRINTING)
    return end_of_printing(FAILURE);
  //the device was not doing an IO.
  return SUCCESS;
}

/* ends the use of the device by its current owner by waking it up. It also release the device and put the return value in the PCB */
int32_t end_of_printing(int32_t code){
  int32_t r;
  PCB* old_owner = owner;
  mode = UNUSED;
  tty0->ier.field.etbei = 0; //stop interrupting whenever the device is ready to print
  //resets the internal queue (in case we quit because of an error)
  reset_internal_queue();
  r = release_uart();
  if(r == FAILURE){
    code = FAILURE; //error 
  }
  //puts the return code directly into the owner's PCB
  pcb_get_registers(old_owner)->v_reg[0] = code;
  return r;
}

/* this function is end_of_printing but adds also a \0 character at the end of input */
int32_t end_of_reading(int32_t code){
  in_buffer[index] = '\0';
  /* we don't need interrupts when data is received anymore */
  tty0->ier.field.erbfi = 0;
  //for the rest, it is the same as end_of_printing
  return end_of_printing(code);
}
